use std::{mem::forget, ptr::{self}, slice, sync::Arc};

use jni::{
    objects::{JByteArray, JObject, JString, ReleaseMode},
    sys::{jbyteArray, jint, jlong, jobject},
    JNIEnv,
};

use crate::{
    error::AppErr,
    serial_port::{new_serial_port, SerialPortConn, SharedConn}, utils::path::IO_EXCEPTION,
};

use super::global::get_rt;

fn open(env: &mut JNIEnv, name: JString, baud_rate: jint, cb: JObject) -> Result<SharedConn, AppErr> {
    let s = unsafe { env.get_string_unchecked(&name).unwrap() };
    let name = s.to_str().unwrap();

    let baud_rate = baud_rate as u32;
    let global_cb = env.new_global_ref(cb).unwrap();
  
    let conn = get_rt().block_on(async { 
        new_serial_port(name, baud_rate, global_cb) 
    })?;

    Ok(conn)
}

#[no_mangle]
pub extern "system" fn Java_rustlib_SerialPort_00024Companion_open(
    mut env: JNIEnv,
    _thiz: jobject,
    name: JString,
    baud_rate: jint,
    cb: JObject
) -> jlong {
    let ret = open(&mut env, name, baud_rate, cb);
    match ret {
        Ok(conn) => return Arc::into_raw(conn) as jlong,
        Err(e) => {
            env.throw_new(IO_EXCEPTION, e.to_string()).unwrap();
        }
    };
    -1
}

fn get_conn(env: &mut JNIEnv, obj: &JObject) -> SharedConn {
    let fd = env.get_field(obj, "nativeApi", "J").unwrap().j().unwrap();
    unsafe { Arc::from_raw(fd as *const SerialPortConn) }
}

#[no_mangle]
pub extern "system" fn Java_rustlib_SerialPort_ping(mut env: JNIEnv, obj: JObject) {
    let conn = get_conn(&mut env, &obj);
    let ret = get_rt().block_on(conn.ping());
    forget(conn);

    if let Err(e) = ret {
        env.throw_new(IO_EXCEPTION, e.to_string()).unwrap();
    }
}

#[no_mangle]
pub extern "system" fn Java_rustlib_SerialPort_simpleReq(
    mut env: JNIEnv,
    obj: JObject,
    cmd: jint,
    data: JByteArray
) -> jbyteArray {

    let conn = get_conn(&mut env, &obj);

    let len = env.get_array_length(&data).unwrap() as usize;
    let data2 = unsafe {
        env.get_array_elements(&data, ReleaseMode::NoCopyBack).unwrap()
    };

    let buf = unsafe {
        slice::from_raw_parts(data2.as_ptr() as * const u8, len)
    }; 

    let ret = get_rt().block_on( conn.simple_req(cmd as u8, buf) );

    forget(conn);

    let frame = match ret {
        Err(e) => {
            env.throw_new(IO_EXCEPTION, e.to_string()).unwrap();
            return ptr::null_mut();
        }  
        Ok(f) => {
            f
        }    
    };
    
    frame.new_jbyte_array(&mut env).as_raw()
}

#[no_mangle]
pub extern "system" fn Java_rustlib_SerialPort_req(
    mut env: JNIEnv,
    obj: JObject,
    cmd: jint,
    data: JByteArray
) -> jbyteArray {
    let conn = get_conn(&mut env, &obj);

    let len = env.get_array_length(&data).unwrap() as usize;
    let data2 = unsafe {
        env.get_array_elements(&data, ReleaseMode::NoCopyBack).unwrap()
    };

    let buf = unsafe {
        slice::from_raw_parts(data2.as_ptr() as * const u8, len)
    }; 

    let ret = get_rt().block_on( conn.request(cmd as u8, buf) );

    forget(conn);

    let frame = match ret {
        Err(e) => {
            env.throw_new(IO_EXCEPTION, e.to_string()).unwrap();
            return ptr::null_mut();
        }  
        Ok(f) => {
            f
        }    
    };
    
    frame.new_jbyte_array(&mut env).as_raw()
}

pub extern "system" fn Java_rustlib_SerialPort_close(
    mut env: JNIEnv,
    obj: JObject
) {
    let conn = get_conn(&mut env, &obj);
    conn.close()
}

